﻿using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Collections.Generic;
using System.Linq;

namespace MaterialEditor
{
    public class TemporalValue
    {
        private bool setType;

        public NodeValueType type;

        private Dictionary<TimeSpan, object> keyedValues = new Dictionary<TimeSpan, object>();

        public event BasicDelegate ValueChanged;

        public TemporalValue()
        {
            Form1.instance.timeline.playbackPannel1.CursorPointMoved += playbackPannel1_CursorPointMoved;
        }

        void playbackPannel1_CursorPointMoved()
        {
            if (ValueChanged != null)
                ValueChanged();
        }

        public T To<T>()
        {
            return (T)CurrentValue;
        }

        private object CurrentValue
        {
            get
            {
                // find what two values we're between
                TimeSpan now = Form1.instance.Now;

                TimeSpan[] keys = keyedValues.Keys.ToArray();
                // if there is only one value, just return that one
                if (keys.Length == 1)
                    return keyedValues.First().Value;

                KeyValuePair<TimeSpan, object> high = new KeyValuePair<TimeSpan, object>(), low = new KeyValuePair<TimeSpan, object>();
                for (int i = 0; i < keys.Length - 1; i++)
                {
                    if (keys[i].Ticks < now.Ticks && keys[i + 1].Ticks > now.Ticks)
                    {
                        low = keyedValues.ToArray()[i];
                        high = keyedValues.ToArray()[i + 1];
                        break;
                    }
                    if (i == keys.Length - 2)
                    {
                        low = keyedValues.ToArray()[i];
                        high = keyedValues.ToArray()[i + 1];
                    }
                }

                // find the weight between the two values
                float weight = (float)(Form1.instance.Now.Ticks - low.Key.Ticks) / (float)(high.Key.Ticks - low.Key.Ticks);
                weight = MathHelper.Clamp(weight, 0.0f, 1.0f);

                return BlendValues(low.Value, high.Value, weight);
            }
        }

        public object Value
        {
            get { return CurrentValue; }

            set
            {
                // if null is passed, fuk dis shit
                if (value == null)
                    return;

                if (!setType)
                {
                    type = GetTypeFromValue(value);
                    setType = true;
                }
                // key the value at the current time
                if (!keyedValues.Keys.Contains(Form1.instance.Now))
                {
                    keyedValues.Add(Form1.instance.Now, value);
                    return;
                }
                else
                    keyedValues[Form1.instance.Now] = value;
            }
        }

        private NodeValueType GetTypeFromValue(object o)
        {
            if (o.GetType() == typeof(float))
                return NodeValueType.ValueFloat;
            else if (o.GetType() == typeof(Color))
                return NodeValueType.ValueColor;
            else if (o.GetType() == typeof(TextureContainer) || o.GetType() == typeof(Texture2D))
                return NodeValueType.ValueTexture;
            else if (o.GetType() == typeof(Vector2))
                return NodeValueType.ValueVector2;
            else if (o.GetType() == typeof(Vector3))
                return NodeValueType.ValueVector3;

            throw new InvalidOperationException();
        }

        private object BlendValues(object a, object b, float weight)
        {
            switch (type)
	        {
		        case NodeValueType.ValueFloat:
                    return MathHelper.Lerp((float)a, (float)b, weight);
                case NodeValueType.ValueTexture:
                    throw new InvalidOperationException();
                case NodeValueType.ValueColor:
                    return Color.Lerp((Color)a, (Color)b, weight);
                case NodeValueType.ValueVector2:
                    return Vector2.Lerp((Vector2)a, (Vector2)b, weight);
                case NodeValueType.ValueVector3:
                    return Vector3.Lerp((Vector3)a, (Vector3)b, weight);
                default:
                    throw new InvalidOperationException();
	        }
        }

        public static implicit operator TemporalValue(float value)
        {
            TemporalValue outval = new TemporalValue();
            outval.Value = value;

            return outval;
        }
        public static implicit operator TemporalValue(Color value)
        {
            TemporalValue outval = new TemporalValue();
            outval.Value = value;

            return outval;
        }
        public static implicit operator TemporalValue(TextureContainer value)
        {
            TemporalValue outval = new TemporalValue();
            outval.Value = value;

            return outval;
        }
        public static implicit operator TemporalValue(Vector2 value)
        {
            TemporalValue outval = new TemporalValue();
            outval.Value = value;

            return outval;
        }
        public static implicit operator TemporalValue(Vector3 value)
        {
            TemporalValue outval = new TemporalValue();
            outval.Value = value;

            return outval;
        }
    }
}